/*
 *  AudioRecorder.h
 *  SoundController0002
 *
 *  Created by jiro on 10/06/28.
 *  Copyright 2010 jiro music. All rights reserved.
 *
 */

#pragma once

#include <vector>
#include <string>
#include <iostream>

#include <Cocoa/Cocoa.h>
#include <AudioToolbox/AudioToolbox.h>

#include "AudioChannel.h"
#include "SoundElement.h"
#include "Utility.h"
#include "SoundElementFactory.h"
#include "Exception.h"

namespace sc 
{	
	template< class AudioData >
	class AudioRecorder
	{
	private:

		std::vector< AudioChannel< AudioData > > audioChannels;

	public:

		AudioRecorder( int channelCount, int trackCount )
		{
			audioChannels.resize( channelCount );
			for ( int i = 0; i < channelCount; i++ )
			{
				audioChannels[ i ].Initialize( trackCount );
			}
		}

		void Record( int channelId, int trackId, double time, const SoundElement< AudioData >* soundElement )
		{
			audioChannels[ channelId ].Record( time, trackId, soundElement );
		}

		void Record( int channelId, int trackId, double time, const std::vector< const SoundElement< AudioData >* >& chord  )
		{
			size_t noteCount = chord.size();
			for ( int i = 0; i < noteCount; i++ ) {
				audioChannels[ channelId ].Record( time, trackId, chord[ i ] );
			}
		}

		/**	record sound element into two channel	*/
		void Record(
			int trackIdChannelOne, int trackIdChannelTwo,
			double time, const SoundElement< AudioData >* soundElement,
			double channelOneDynamicsAdjuster, double channelTwoDynamicsAdjuster )
		{
			const SoundElement< AudioData >* elementToChannelOne =
				SoundElementFactory< AudioData >::CreateWithDynamicsAdjuster( soundElement, channelOneDynamicsAdjuster );
			const SoundElement< AudioData >* elementToChannelTwo =
				SoundElementFactory< AudioData >::CreateWithDynamicsAdjuster( soundElement, channelTwoDynamicsAdjuster );
			Record( 0, trackIdChannelOne, time, elementToChannelOne );
			Record( 1, trackIdChannelTwo, time, elementToChannelTwo );
		}

		void Record(
			int trackIdChannelOne, int trackIdChannelTwo,
			double time, std::vector< const SoundElement< AudioData >* > chord,
			double channelOneDynamicsAdjuster, double channelTwoDynamicsAdjuster )
		{
			size_t count = chord.size();
			for ( int i = 0; i < count; i++ ) {
				const SoundElement< AudioData >* elementToChannelOne =
					SoundElementFactory< AudioData >::CreateWithDynamicsAdjuster( chord[ i ], channelOneDynamicsAdjuster );
				const SoundElement< AudioData >* elementToChannelTwo =
					SoundElementFactory< AudioData >::CreateWithDynamicsAdjuster( chord[ i ], channelTwoDynamicsAdjuster );
				Record( 0, trackIdChannelOne, time, elementToChannelOne );
				Record( 1, trackIdChannelTwo, time, elementToChannelTwo );
			}
		}

		void Record( int channelId, int trackId, const AudioElement< AudioData >& audioData )
		{
			audioChannels[ channelId ].Record( trackId, audioData );
		}

		void Record( 
			int trackIdChannelOne, int trackIdChannelTwo,
			const AudioElement< AudioData >& audioData, 
			double channelOneDynamicsAdjuster, double channelTwoDynamicsAdjuster )
		{
			AudioElement< AudioData > audioDataToChannelOne = audioData;
			AudioElement< AudioData > audioDataToChannelTwo = audioData;
			audioDataToChannelOne.AdjustDynamics( channelOneDynamicsAdjuster );
			audioDataToChannelTwo.AdjustDynamics( channelTwoDynamicsAdjuster );
			Record( 0, trackIdChannelOne, audioDataToChannelOne );
			Record( 1, trackIdChannelTwo, audioDataToChannelTwo );
		}

		const std::vector< AudioChannel< AudioData > >& GetAudioChannels() const
		{
			return audioChannels;
		}

		int GetChannelCount() const
		{
			return static_cast<int>( audioChannels.size() );
		}
		
		int GetTrackCount() const
		{
			if ( audioChannels.size() == 0 )
			{
				return 0;
			}
			return audioChannels[ 0 ].GetTrackCount();
		}

		void GetAudioData( int channelId, std::vector< AudioData >& audioData ) const
		{
			audioData.clear();
			audioChannels[ channelId ].GetAudioData( audioData );
		}

		void GetAudioData( int channelId, int trackId, std::vector< AudioData >& audioData ) const
		{
			audioData.clear();
			audioChannels[ channelId ].GetAudioData( trackId, audioData );
		}

		bool OutputAudioFile( const std::string& path )
		{
			return OutputAudioFile( kAudioFileWAVEType, path );
		}
		
		bool OutputAudioFile( AudioFileTypeID audioFileTypeId, const std::string& path )
		{
			if ( audioFileTypeId != kAudioFileWAVEType )
			{
				throw Exception( "not support file type" );
			}
			
			//	---- init AudioStreamBasicDescription
			AudioStreamBasicDescription description = { 0 };
			description.mSampleRate = Sampling_Rate_As_Double;
			description.mFormatID = kAudioFormatLinearPCM;
			description.mFormatFlags = kAudioFormatFlagIsPacked | kAudioFormatFlagIsFloat;
//			description.mBitsPerChannel = 16;
			description.mBitsPerChannel = 32;
			description.mChannelsPerFrame = 2;
			description.mFramesPerPacket = 1;
			description.mBytesPerFrame = ( description.mBitsPerChannel / 8 ) * description.mChannelsPerFrame;
			description.mBytesPerPacket = description.mBytesPerFrame * description.mFramesPerPacket;
			description.mReserved = 0;
			// ----

			// ---- create audio file
			id url = [ NSURL URLWithString: [ NSString stringWithCString: path.c_str() encoding: NSASCIIStringEncoding ] ];
			ExtAudioFileRef fileRef;
			OSStatus status;
			status = ExtAudioFileCreateWithURL( (CFURLRef) url, audioFileTypeId, & description, 0, kAudioFileFlags_EraseFile, & fileRef );
			if ( status != noErr )
			{
				return false;
			}
			// ----
			
			//	---- get audio data 
			std::vector< std::vector< AudioData > > audioDataList( GetChannelCount() );
			for ( int i = 0; i < GetChannelCount(); i++ )
			{
				GetAudioData( i, audioDataList[ i ] );
			}
			if ( audioDataList.size() == 1 ) 
			{
				audioDataList.push_back( std::vector< AudioData >() );
			}
			for ( size_t i = 2; i < audioDataList.size(); i++ )
			{	//	combine audio data
				int baseListIndex = i % 2;
				if ( audioDataList[ baseListIndex ].size() < audioDataList[ i ].size() )
				{
					int count = audioDataList[ i ].size() - audioDataList[ baseListIndex ].size();
					audioDataList[ baseListIndex ].insert( audioDataList[ baseListIndex ].end(), count, static_cast<AudioData>( 0.0 ) );
				}
				for ( int j = 0; j < audioDataList[ i ].size(); j++ )
				{
					audioDataList[ baseListIndex ][ j ] += audioDataList[ i ][ j ];
				}				
			}
			if ( audioDataList[ 0 ].size() < audioDataList[ 1 ].size() )
			{
				int count = audioDataList[ 1 ].size() - audioDataList[ 0 ].size();
				audioDataList[ 0 ].insert( audioDataList[ 0 ].end(), count, static_cast<AudioData>( 0.0 ) );
			}
			else if ( audioDataList[ 0 ].size() > audioDataList[ 1 ].size() )
			{
				int count = audioDataList[ 0 ].size() - audioDataList[ 1 ].size();
				audioDataList[ 1 ].insert( audioDataList[ 1 ].end(), count, static_cast<AudioData>( 0.0 ) );
			}
			//	----

			//	---- create audio buffer 
			int dataCount = 2 * audioDataList[ 0 ].size();
			float* dataBuffer = new float[ dataCount ];
			memset( dataBuffer, 0x00, dataCount * sizeof( float ) );
			AudioBufferList audioBufferList;
			audioBufferList.mNumberBuffers = 1;
			audioBufferList.mBuffers[ 0 ].mDataByteSize = dataCount;
			audioBufferList.mBuffers[ 0 ].mNumberChannels = 2;
			audioBufferList.mBuffers[ 0 ].mData = dataBuffer;
			int dataIndex = 0;
			int bufferIndex = 0;
			for ( ; dataIndex < dataCount / 2; dataIndex++, bufferIndex += 2 )
			{
				dataBuffer[ bufferIndex ] = audioDataList[ 0 ][ dataIndex ];
				dataBuffer[ bufferIndex + 1 ] = audioDataList[ 1 ][ dataIndex ];
			}
			//	----

			//	---- write data 
			ExtAudioFileWrite( fileRef, dataCount / 2, & audioBufferList );
			//	----

			//	---- post process
			delete [] dataBuffer;
			ExtAudioFileDispose( fileRef );
			//	----
			
			return true;
		}
	};

	typedef AudioRecorder< AudioData32 > AudioRecorder32;

	typedef AudioRecorder< AudioData64 > AudioRecorder64;

}
